// Copyright 2010-2012, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_AMEFILE_H_
#define LIBV_AMEFILE_H_

#include <string>
#include <stdexcept>
#include <libv/acfile.h>
#include <libv/cast.h>
#include <libv/define.h>
#include <libv/error_msg.h>
#include <libv/file/basic_file.h>
#include <libv/lock.h>
#include <libv/lock_mutex.h>
#include <libv/suppress.h>
#include <libv/uncopyable.h>



#define LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION(F) {                            \
    const ::std::string err_str = OsErrorCode2String(file_.last_error());    \
    throw AMEFileException(::std::string(#F) + " failed: " + err_str);       \
}

#define LIBV_AMEFILE_THROW_IF_FALSE_0(obj,mthd)                              \
    if ( !(obj).mthd() ) LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION(mthd)

#define LIBV_AMEFILE_THROW_IF_FALSE_1(obj,mthd,a)                            \
    if ( !(obj).mthd(a) ) LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION(mthd)

#define LIBV_AMEFILE_THROW_IF_FALSE_2(obj,mthd,a,b)                          \
    if ( !(obj).mthd(a,b) ) LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION(mthd)

#define LIBV_AMEFILE_THROW_IF_FALSE_3(obj,mthd,a,b,c)                        \
    if ( !(obj).mthd(a,b,c) ) LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION(mthd)

#define LIBV_AMEFILE_THROW_IF_FALSE_4(obj,mthd,a,b,c,d)                      \
    if (!(obj).mthd(a,b,c,d)) LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION(mthd)

#define LIBV_AMEFILE_THROW_IF_NEGATIVE(value,mthd)                           \
    if ( value < 0 ) LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION(mthd)



namespace libv
{

/** Базовое исключение для всех ошибок, возникающих при работе с файлами. */
LIBV_DEFINE_SIMPLE_EXCEPTION(AMEFileException)



/** Потокобезопасная обертка над SFile и ACFile генерирующая исключения
  * AMEFileExpcetion в случае ошибки. */
class AMEFile : libv::Uncopyable
{
  public:
    typedef BasicFile::OpenType OpenType;
    typedef BasicFile::LockType LockType;
    typedef BasicFile::PreallocType PreallocType;
    typedef BasicFile::Prealloc Prealloc;

    /** Обертка над ACFile::RequestCompletionHandle,
      * генерирующая исключения при возникновении ошибок. */
    // Наследование использовано, чтобы получить enum Status в данном классе.
    class RequestCompletionHandle : public BasicRequestHandle
    {
      friend class AMEFile;
      private:
        typedef ACFile::RequestCompletionHandle Impl;

      public:
        enum Status Status() const { return impl_.Status(); }

        void WaitUntilFinished() const
        {
            if ( !impl_.WaitUntilFinished() || Status() != Impl::COMPLETED )
                throw AMEFileException(OsErrorCode2String(impl_.last_error()));
        }

        void Verify() const
        {
            const enum Status s = impl_.Status();
            if ( s == FAILED || s == CANCELED )
                throw AMEFileException(OsErrorCode2String(impl_.last_error()));
        }

        void Clear() { return impl_.Clear(); }

      private:
        Impl impl_;
    };


    void Open(const ::std::string& file_path,
              const int open_type,
              const bool enable_read_ahead_caching = true)
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_3(file_,
                                      Open,
                                      file_path,
                                      open_type,
                                      enable_read_ahead_caching);
        LIBV_AMEFILE_THROW_IF_FALSE_1(file_, Lock, LockType::kExclusiveLock);
    }


    void Open(const ::std::string& file_path,
              const int open_type,
              const bool enable_read_ahead_caching = true) const
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_3(file_,
                                      Open,
                                      file_path,
                                      open_type,
                                      enable_read_ahead_caching);
        LIBV_AMEFILE_THROW_IF_FALSE_1(file_, Lock, LockType::kExclusiveLock);
    }


    void Close()
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_0(file_, Close);
    }


    void Close() const
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_0(file_, Close);
    }


    bool is_opened() const
    {
        libv::Lock dnd(&mutex_);
        return file_.is_opened();
    }


    ::std::string file_path() const
    {
        libv::Lock dnd(&mutex_);
        return file_.file_path();
    }


    uint64_t Size() const
    {
        libv::Lock dnd(&mutex_);

        const fpos64 r = file_.Size();
        LIBV_AMEFILE_THROW_IF_NEGATIVE(r, Size);
        return libv::IntegerCast<uint64_t>(r);
    }


    void Resize(const fpos64 max_off)
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_1(file_, Resize, max_off);
    }


    bool auto_remove() const
    {
        libv::Lock dnd(&mutex_);
        return file_.auto_remove();
    }


    void set_auto_remove(const bool on)
    {
        libv::Lock dnd(&mutex_);
        file_.set_auto_remove(on);
    }


    Prealloc prealloc() const
    {
        libv::Lock dnd(&mutex_);
        return file_.prealloc();
    }


    void set_prealloc(const Prealloc& prealloc)
    {
        libv::Lock dnd(&mutex_);
        file_.set_prealloc(prealloc);
    }


    void PreallocateAtLeast(const fpos64 max_off)
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_1(file_, PreallocateAtLeast, max_off);
    }


    void Write(fpos64* offset, const void* data, const size_t data_size)
    {
        if ( data_size == 0 ) return;  // для упрощения использующего кода

        libv::Lock dnd(&mutex_);

        RequestCompletionHandle h;
        LIBV_AMEFILE_THROW_IF_FALSE_4(file_, Write,
                                      *offset, data,
                                      libv::IntegerCast<uint32_t>(data_size),
                                      &h.impl_);
        h.WaitUntilFinished();
        *offset += data_size;
    }


    void Read(fpos64* offset, void* buffer, const size_t buffer_size) const
    {
        if ( buffer_size == 0 ) return;  // для упрощения использующего кода

        libv::Lock dnd(&mutex_);

        RequestCompletionHandle h;
        LIBV_AMEFILE_THROW_IF_FALSE_4(file_, Read,
                                      *offset, buffer, 
                                      libv::IntegerCast<uint32_t>(buffer_size),
                                      &h.impl_);
        h.WaitUntilFinished();
        *offset += buffer_size;
    }


    void Write(const fpos64& offset, const void* data, const size_t data_size,
               RequestCompletionHandle* return_h)
    {
        if ( data_size == 0 ) return;  // для упрощения использующего кода

        libv::Lock dnd(&mutex_);

        RequestCompletionHandle h;
        LIBV_AMEFILE_THROW_IF_FALSE_4(file_,Write,
                                      offset, data,
                                      libv::IntegerCast<uint32_t>(data_size),
                                      &h.impl_);

        *return_h = h;
    }


    void Read(const fpos64& offset,
              volatile void* buffer, const size_t buffer_size,
              RequestCompletionHandle* return_h) const
    {
        if ( buffer_size == 0 ) return;  // для упрощения использующего кода

        libv::Lock dnd(&mutex_);

        RequestCompletionHandle h;
        LIBV_AMEFILE_THROW_IF_FALSE_4(file_, Read,
                                      offset, buffer,
                                      libv::IntegerCast<uint32_t>(buffer_size),
                                      &h.impl_);

        *return_h = h;
    }


    void Flush()
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_0(file_, Flush);
    }


    bool TryLock(const int lock_type) const
    {
        libv::Lock dnd(&mutex_);


        if ( file_.TryLock(lock_type) )
            return true;

        if ( file_.last_error() == 0 )
            return false;

        LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION("TryLock");
        LIBV_SUPPRESS_INTEL_MISSING_RETURN(false);
    }


    void Lock(const int lock_type) const
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_1(file_, Lock, lock_type);
    }


    void Unlock() const
    {
        libv::Lock dnd(&mutex_);
        LIBV_AMEFILE_THROW_IF_FALSE_0(file_, Unlock);
    }


    int lock_type() const
    {
        libv::Lock dnd(&mutex_);
        return file_.lock_type();
    }


  private:
    libv::ACFile file_;
    mutable libv::Mutex mutex_;
};

}  // libv


#undef LIBV_AMEFILE_THROW_AMEFILE_EXCEPTION
#undef LIBV_AMEFILE_THROW_IF_FALSE_0
#undef LIBV_AMEFILE_THROW_IF_FALSE_1
#undef LIBV_AMEFILE_THROW_IF_FALSE_2
#undef LIBV_AMEFILE_THROW_IF_FALSE_3
#undef LIBV_AMEFILE_THROW_IF_FALSE_4

#endif  // LIBV_AMEFILE_H_
